1.Introduction XML | 2.Model XML | 3.View XML | 4.Tab XML | 5.Mapping XML | 6.Aspects XML

Chapter 5: Object/relational mapping XML (classic)

Object relational mapping allows you to declare in which tables and columns of your database the component data will be stored.
If ORM is familiar to you: The OpenXava mapping is used to generate the code and XML files needed to object/relational mapping. Actually the code is generated for:
  • Hibernate 3.x.
  • EntityBeans CMP 2 of JBoss 3.2.x y 4.0.x.
  • EntityBeans CMP 2 of Websphere 5, 5.1y 6.
If Object/relational tools are not familiar to you: Object/relational tools allow you to work with objects instead of tables and columns, and to generate automatically the SQL code to read and update the database.
OpenXava generates a set of Java classes that represent the model layer of your application (the business concepts with its data and its behavior). You can work directly with these objects, and you do not need direct access to the SQL database. Of course you have to define precisely how to map your classes to your tables, and this work is done in the mapping part.

Entity mapping

The syntax to map the main entity is:
<entity-mapping table="table"> <!-- 1 -->
 <property-mapping ... /> ... <!-- 2 -->
 <reference-mapping ... /> ... <!-- 3 -->
 <multiple-property-mapping ... /> ... <!-- 4 -->
</entity-mapping>
  1. table (required): Maps this table to the main entity of component.
  2. property-mapping (several, optional): Maps a property to a column in the database table.
  3. reference-mapping (several, optional): Maps a reference to one or more columns in the database table.
  4. multiple-property-mapping (several, optional): Maps a property to several columns in database table.
A plain example can be:
<entity-mapping table="XAVATEST@separator@DELIVERYTYPE">
 <property-mapping property="number" column="NUMBER"/>
 <property-mapping property="description" column="DESCRIPTION" />
</entity-mapping>
Easier impossible.
You see how the table name is qualified (with collection/schema name included). Also you see that the separator is @separator@ instead of a dot (.), this is useful because you can define the separator value in your build.xml and thus the same application can run against databases with or without support for collections or schemes.

Property mapping

The syntax to map a property is:
<property-mapping
 property="property" <!-- 1 -->
 column="column" <!-- 2 -->
 cmp-type="type" <!-- 3 -->
 formula="formula"> <!-- 4 New in v3.1.4 -->
 <converter ... /> <!-- 5 -->
</property-mapping>
  1. property (required): Name of a property defined in the model part.
  2. column (optional since v3.1.4): Name of a table column. If omitted the property name is assumed as column name.
  3. cmp-type (optional): Java type of the attribute used internally in your object to store the property value. This allows you to use Java types more closer to the database, without contaminating your Java model. It is used with a converter.
  4. formula (optional): (New in v3.1.4) To calculate the value of the property using the database. It must be a valid SQL fragment. If you use formula you can omit the column attribute. It does not work with EntityBeans CMP2.
  5. converter (one, optional): Implements your custom logic to convert from Java to DB format and vice versa.
For now, you have seen simple examples for mapping a property to a column. A more advanced case is using a converter. A converter is used when the Java type and the DB type don't match, in this case a converter is a good idea. For example, imagine that in database the zip code is VARCHAR while in Java you want to use an int. A Java int is not directly assignable to a VARCHAR column in database, but you can use a converter to transform that int to String. Let's see it:
<property-mapping property="address_zipCode" column="ZIPCODE" cmp-type="String">
 <converter class="org.openxava.converters.IntegerStringConverter"/>
</property-mapping>
cmp-type indicates to which type the converter has to convert to and it is the type of the internal attribute in the generated class code. It must be a type close (assignable directly from JDBC) to the column type in database.
The converter code is:
package org.openxava.converters;
 
/**
 * In java an int and in database a String.
 *
 * @author Javier Paniza
 */
public class IntegerStringConverter implements IConverter { // 1
 
 private final static Integer ZERO = new Integer(0);
 
 public Object toDB(Object o) throws ConversionException { // 2
 return o==null?"0":o.toString();
 }
 
 public Object toJava(Object o) throws ConversionException { // 3
 if (o == null) return ZERO;
 if (!(o instanceof String)) {
 throw new ConversionException("conversion_java_string_expected");
 }
 try {
 return new Integer((String) o);
 }
 catch (Exception ex) {
 ex.printStackTrace();
 throw new ConversionException("conversion_error");
 }
 }
 
}
A converter must implement IConverter (1), this forces it to have a toDB() (2) method, that receives the object of the type used in Java (in this case an Integer) and returns its representation using a type closer to the database (in this case String hence assignable to a VARCHAR column). The toJava() method has the opposite goal; it gets the object in database format and it must return an object of the type used in Java.
If there are any problem you can throw a ConversionException.
You see that this converter is in org.openxava.converters, i. e., it is a generic converter that comes with the OpenXava distribution. Another quite useful generic converter is ValidValuesLetterConverter. That one allows to map properties of type valid-values. For example, if you have a property like this:
<entity>
...
 <property name="distance">
 <valid-values>
 <valid-value value="local"/>
 <valid-value value="national"/>
 <valid-value value="international"/>
 </valid-values>
 </property>
...
</entity>
valid-values generates a Java property of type int in which 0 is used to indicate the empty value, 1 is 'local', 2 is 'national' and 3 is 'international'. But what happens, if in the database a single letter ('L', 'N' or 'I') is stored? In this case you can use a mapping like this:
<property-mapping property="distance" column="DISTANCE" cmp-type="String">
 <converter class="org.openxava.converters.ValidValuesLetterConverter">
 <set property="letters" value="LNI"/>
 </converter>
</property-mapping>
As you put 'LNI' as a value to letters, the converter matches the 'L' to 1, the 'N' to 2 and the 'I' to 3. You also see how converters are configurable using its properties and this makes the converters more reusable (as calculators, validators, etc).

Using formula (new in v3.1.4) attribute you can define a calculation for your property. This calculation is expressed using SQL, and it is executed by the database, not by Java. You only need to write a valid SQL fragment:
<property-mapping property="unitPriceWithTax" formula="UNITPRICE * 1.16"/>
The use is simple. Put your calculation exactly in the same way that you would put it in a SQL statement.
The properties with formula are read only properties. When the object is read from database the calculation is done by the database and the property is populate with it.
This is an alternative to calculated properties. It has the advantage that the user can filter by this property in list mode, and the disadvantage that you have to use SQL instead of Java, and you have no live recalculation of the value.

Reference mapping

The syntax to map a reference is:
<reference-mapping
 reference="reference" <!-- 1 -->
>
 <reference-mapping-detail ... /> ... <!-- 2 -->
</reference-mapping>
  1. reference (required): The reference to map.
  2. reference-mapping-detail (several, required): Maps a table column to a property of the reference key. If the key of the referenced object is multiple, then you will have several reference-mapping-detail.
Making a reference mapping is easy. For example, if you have a reference like this:
<entity>
 ...
 <reference name="invoice" model="Invoice"/>
 ...
</entity>
You can map it this way:
<entity-mapping table="XAVATEST@separator@DELIVERY">
 <reference-mapping reference="invoice">
 <reference-mapping-detail
 column="INVOICE_YEAR"
 referenced-model-property="year"/>
 <reference-mapping-detail
 column="INVOICE_NUMBER"
 referenced-model-property="number"/>
 </reference-mapping>
 ...
</entity-mapping>
INVOICE_YEAR and INVOICE_NUMBER are columns of the DELIVERY table that allows accessing to its invoice, that is it's the foreign key although declaring it as a foreign key in database is not required. You must map this columns to the key properties in Invoice, like this:
<entity>
 <property name="year" type="int" key="true" size="4" required="true">
 <default-value-calculator
 class="org.openxava.calculators.CurrentYearCalculator"/>
 </property>
 <property name="number" type="int" key="true" size="6" required="true"/>
 ...
If you have a reference to a model which key itself includes references, you can define it in this way:
<reference-mapping reference="delivery">
 <reference-mapping-detail
 column="DELIVERY_INVOICE_YEAR"
 referenced-model-property="invoice.year"/>
 <reference-mapping-detail
 column="DELIVERY_INVOICE_NUMBER"
 referenced-model-property="invoice.number"/>
 <reference-mapping-detail
 column="DELIVERY_TYPE"
 referenced-model-property="type.number"/>
 <reference-mapping-detail
 column="DELIVERY_NUMBER"
 referenced-model-property="number"/>
</reference-mapping>
As you see, to indicate the properties of referenced models you can qualify them.
Also it's possible to use converters in a reference mapping:
<reference-mapping reference="drivingLicence">
 <reference-mapping-detail
 column="DRIVINGLICENCE_TYPE"
 referenced-model-property="type"
 cmp-type="String"> <!-- 1 In this case this line can be omitted -->
 <converter class="org.openxava.converters.NotNullStringConverter"/> <!-- 2 -->
 </reference-mapping-detail>
 <reference-mapping-detail
 column="DRIVINGLICENCE_LEVEL"
 referenced-model-property="level"/>
</reference-mapping>
You can use the converter just like in a simple property (2). The difference in the reference case is that if you do not define a converter, then a default converter is not used. This is because applying in an indiscriminate way converters on keys can produce problems in some circumstances. You can use cmp-type (1) (new in v2.0) to indicate the Java type of the attribute used internally in your object to store the value. This allows you to use Java types closer to the database; cmp-type is not needed if the database type is compatible with Java type.

Multiple property mapping

With <multiple-property-mapping/> you can map several table columns to a single Java property. This is useful if you have properties of custom class that have itself several attributes to store. Also it is used when you have to deal with legate database schemes.
The syntax for this type of mapping is:
<multiple-property-mapping
 property="property" <!-- 1 -->
>
 <converter ... /> <!-- 2 -->
 <cmp-field ... /> ... <!-- 3 -->
</multiple-property-mapping>
  1. property (required): Name of the property to map.
  2. converter (one, required): Implements the logic to convert from Java to the database and vice versa. Must implement IMultipleConverter.
  3. cmp-field (several, required): Maps each column in the database with a property of a converter.
A typical example is the generic converter Date3Converter, that allows to store in the database 3 columns and in Java a single property of type java.util.Date.
<multiple-property-mapping property="deliveryDate">
 <converter class="org.openxava.converters.Date3Converter"/>
 <cmp-field converter-property="day" column="DAYDELIVERY" cmp-type="int"/>
 <cmp-field converter-property="month" column="MONTHDELIVERY" cmp-type="int"/>
 <cmp-field converter-property="year" column="YEARDELIVERY" cmp-type="int"/>
</multiple-property-mapping>
DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that store the delivery date, and day, month and year are properties of Date3Converter. And here Date3Converter:
package org.openxava.converters;
 
import java.util.*;
 
import org.openxava.util.*;
 
/**
 * In java a <tt>java.util.Date</tt> and in database 3 columns of
 * integer type. <p>
 *
 * @author Javier Paniza
 */
public class Date3Converter implements IMultipleConverter { <!-- 1 -->
 
 private int day;
 private int month;
 private int year;
 
 public Object toJava() throws ConversionException { <!-- 2 -->
 return Dates.create(day, month, year);
 }
 
 public void toDB(Object javaObject) throws ConversionException { <!-- 3 -->
 if (javaObject == null) {
 setDay(0);
 setMonth(0);
 setYear(0);
 return;
 }
 if (!( javaObject instanceof java.util.Date)) {
 throw new ConversionException("conversion_db_utildate_expected");
 }
 java.util.Date date = (java.util.Date) javaObject;
 Calendar cal = Calendar.getInstance();
 cal.setTime(date);
 setDay(cal.get(Calendar.DAY_OF_MONTH));
 setMonth(cal.get(Calendar.MONTH) + 1);
 setYear(cal.get(Calendar.YEAR));
 }
 
 public int getYear() {
 return year;
 }
 
 public int getDay() {
 return day;
 }
 
 public int getMonth() {
 return month;
 }
 
 public void setYear(int i) {
 year = i;
 }
 
 public void setDay(int i) {
 day = i;
 }
 
 public void setMonth(int i) {
 month = i;
 }
 
}
This converter must implement IMultipleConverter (1). This forces it to have a toJava() (2) method that must return a Java object from its property values (in this case year, month and day). The returned object is the mapped property (in this case deliveryDate). The calculator must have the method toDB() (3) too; this method receives the value of the property (a delivery date) and has to split it and to put the result in the converter properties (year, month and day).

Reference to aggregate mapping

A reference to an aggregate contains data that in the relational model are stored in the same table as the main entity. For example, if you have an aggregate Address associated to a Customer, the address data is stored in the same data table as the customer data. How can you map this case with OpenXava?
Let's see. In the model you can have:
<entity>
 ...
 <reference name="address" model="Address" required="true"/>
 ...
</entity>
 
<aggregate name="Address">
 <implements interface="org.openxava.test.ejb.IWithCity"/>
 <property name="street" type="String" size="30" required="true"/>
 <property name="zipCode" type="int" size="5" required="true"/>
 <property name="city" type="String" size="20" required="true"/>
 <reference name="state" required="true"/>
</aggregate>
Simply a reference to an aggregate. And for mapping it you can do:
<entity-mapping table="XAVATEST@separator@CUSTOMER">
 ...
 <property-mapping property="address_street" column="STREET"/>
 <property-mapping property="address_zipCode" column="ZIPCODE" cmp-type="String">
 <converter class="org.openxava.converters.IntegerStringConverter"/>
 </property-mapping>
 <property-mapping property="address_city" column="CITY"/>
 <reference-mapping reference="address_state">
 <reference-mapping-detail column="STATE" referenced-model-property="id"/>
 </reference-mapping>
</entity-mapping>
You see how the aggregate members are mapped within the entity mapping that contains it. The only thing that you have to do is using the name of the reference as a prefix with an underline (in this case address_). You can observe that in the case of aggregates you can map references, properties and that you can use converters in the usual way.

Aggregate used in collection mapping

In case that you have a collection of aggregates, for example the invoice details, obviously the detail data is stored in a different table as the heading data. In this case the aggregate must have its own mapping. Let's see the example:
Here the model part of Invoice:
<entity>
 ...
 <collection name="details" minimum="1">
 <reference model="InvoiceDetail"/>
 </collection>
 ...
</entity>
 
<aggregate name="InvoiceDetail">
 <property name="oid" type="String" key="true" hidden="true">
 <default-value-calculator
 class="org.openxava.test.calculators.InvoiceDetailOidCalculator"
 on-create="true"/>
 </property>
 <property name="serviceType">
 <valid-values>
 <valid-value value="special"/>
 <valid-value value="urgent"/>
 </valid-values>
 </property>
 <property name="quantity" type="int" size="4" required="true"/>
 <property name="unitPrice" stereotype="MONEY" required="true"/>
 <property name="amount" stereotype="MONEY">
 <calculator class="org.openxava.test.calculators.DetailAmountCalculator">
 <set property="unitPrice"/>
 <set property="quantity"/>
 </calculator>
 </property>
 <reference model="Product" required="true"/>
 <property name="deliveryDate" type="java.util.Date">
 <default-value-calculator
 class="org.openxava.calculators.CurrentDateCalculator"/>
 </property>
 <reference name="soldBy" model="Seller"/>
 <property name="remarks" stereotype="MEMO"/>
</aggregate>
You can see a collection of InvoiceDetail which is an aggregate. InvoiceDetail has to be mapped this way:
<aggregate-mapping aggregate="InvoiceDetail" table="XAVATEST@separator@INVOICEDETAIL">
 <reference-mapping reference="invoice"> <!-- 1 -->
 <reference-mapping-detail
 column="INVOICE_YEAR"
 referenced-model-property="year"/>
 <reference-mapping-detail
 column="INVOICE_NUMBER"
 referenced-model-property="number"/>
 </reference-mapping>
 <property-mapping property="oid" column="OID"/>
 <property-mapping property="serviceType" column="SERVICETYPE"/>
 <property-mapping property="unitPrice" column="UNITPRICE"/>
 <property-mapping property="quantity" column="QUANTITY"/>
 <reference-mapping reference="product">
 <reference-mapping-detail
 column="PRODUCT_NUMBER"
 referenced-model-property="number"/>
 </reference-mapping>
 <multiple-property-mapping property="deliveryDate">
 <converter class="org.openxava.converters.Date3Converter"/>
 <cmp-field
 converter-property="day" column="DAYDELIVERY" cmp-type="int"/>
 <cmp-field
 converter-property="month" column="MONTHDELIVERY" cmp-type="int"/>
 <cmp-field
 converter-property="year" column="YEARDELIVERY" cmp-type="int"/>
 </multiple-property-mapping>
 <reference-mapping reference="soldBy">
 <reference-mapping-detail
 column="SOLDBY_NUMBER"
 referenced-model-property="number"/>
 </reference-mapping>
 <property-mapping property="remarks" column="REMARKS"/>
</aggregate-mapping>
The aggregate mapping must be below of the main entity mapping. A component must have as many aggregate mappings as aggregates used in collections. The aggregate mapping has the same possibilities than entity mapping, with the exception that it's required to map a reference to the container object although maybe this reference is not defined in model. That is, although you do not define a reference to Invoice in InvoiceDetail OpenXava adds it automatically and you must map it (1).

Converters by default

You have seen how to declare a converter in a property mapping. But what happens, if you do not declare a converter? In reality in OpenXava all properties (except the key properties) have a converter by default. The default converters are defined in OpenXava/xava/default-converters.xml, that has a content like this:
<?xml version = "1.0" encoding = "ISO-8859-1"?>
 
<!DOCTYPE converters SYSTEM "dtds/converters.dtd">
 
<!--
In your project use the name 'converters.xml' or 'conversores.xml'
-->
 
<converters>
 
 <for-type type="java.lang.String"
 converter-class="org.openxava.converters.TrimStringConverter"
 cmp-type="java.lang.String"/>
 
 <for-type type="int"
 converter-class="org.openxava.converters.IntegerNumberConverter"
 cmp-type="java.lang.Integer"/>
 
 <for-type type="java.lang.Integer"
 converter-class="org.openxava.converters.IntegerNumberConverter"
 cmp-type="java.lang.Integer"/>
 
 <for-type type="boolean"
 converter-class="org.openxava.converters.Boolean01Converter"
 cmp-type="java.lang.Integer"/>
 
 <for-type type="java.lang.Boolean"
 converter-class="org.openxava.converters.Boolean01Converter"
 cmp-type="java.lang.Integer"/>
 
 <for-type type="long"
 converter-class="org.openxava.converters.LongNumberConverter"
 cmp-type="java.lang.Long"/>
 
 <for-type type="java.lang.Long"
 converter-class="org.openxava.converters.LongNumberConverter"
 cmp-type="java.lang.Long"/>
 
 <for-type type="java.math.BigDecimal"
 converter-class="org.openxava.converters.BigDecimalNumberConverter"
 cmp-type="java.math.BigDecimal"/>
 
 <for-type type="java.util.Date"
 converter-class="org.openxava.converters.DateUtilSQLConverter"
 cmp-type="java.sql.Date"/>
 
</converters>
If you use a property of a type that is not defined here, by default OpenXava will assign the converter NoConversionConverter, a silly converter that don't perform anything.
In the case of key properties and references no converter are assigned; applying a converter to key properties can be problematic in certain circumstances, but even if you want to perform a conversion you can declare a converter explicitly in your mapping.
If you wish to modify the behavior of default converters in your application, you do not modify the OpenXava file, but you create your own converters.xml file in the folder xava of your project. You can assign a converter by default to a stereotype (using <for-stereotype/>).

Default mapping (new in v2.1.3)

Since version 2.1.3 OpenXava allows to define components without mapping, and it assumed a default mapping for it. For example, you can write:
<?xml version="1.0" encoding="ISO-8859-1"?>
 
<!DOCTYPE component SYSTEM "dtds/component.dtd">
 
<component name="Pupil">
 
 <entity>
 <property name="number" type="int" key="true"
 size="2" required="true"/>
 <property name="name" type="String"
 size="40" required="true"/>
 <reference name="teacher"/>
 </entity>
 
</component>
This component is mapped to the table Pupil, and the properties number and name are mapped to the columns number and name. The reference teacher is mapped to a column named teacher_number (if the key property of Teacher is named number).

Object/relational philosophy

OpenXava has been born and has been developed in an environment when it was necessary to work with legacy database without changing its structure. The result is that OpenXava:
  • Provides great flexibility when mapping with legacy database.
  • Does not provide some features natural for OOT and that requires to change database scheme, as inheritance support or polymorphic queries.
Another cool feature of OpenXava mapping is that applications are 100% portables from JBoss CMP2 to Websphere CMP2 without writing a single line of code. Furthermore, the portability between Hibernate, JPA and EJB2 version of an application is very high, the mapping and all automatic controllers are 100% portable, obviously the custom EJB2, JPA or Hibernate code is not so portable.